// --- Copyright (c) 2006-2008 Stefan Kyntchev ---
// This software is written and copyrighted by Stefan Kyntchev 
// and BeyondPod Team members. All rights are reserved.
// Author contact: support@beyondpod.mobi
// ------------------------------------------------------------------------
// This file is part of BeyondPod RSS Feed Reader and Podcast manager
// (www.codeplex.com/BeyondPod) 
// BeyondPod is free software: you can redistribute it and/or modify 
// it under the terms of the GNU General Public License as published by 
// the Free Software Foundation, either version 3 of the License, or 
// (at your option) any later version. 
//  
// BeyondPod is distributed in the hope that it will be useful, 
// but WITHOUT ANY WARRANTY; without even the implied warranty of 
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
// GNU General Public License for more details. 
//  
// You should have received a copy of the GNU General Public License
// along with BeyondPod. If not, see <http://www.gnu.org/licenses/>
// ------------------------------------------------------------------------
// COPYRIGHT NOTICE: This copyright notice may NOT be removed, obscured or modified 
// without written consent from the author.
// --- End copyright notice --- 


using System;
using System.IO;
using svs.Mobile.BeyondMedia.FeedCore.RSS.Aggregators;
using svs.Mobile.BeyondMedia.FeedCore.RSS.Entities;
using svs.Mobile.BeyondMedia.FeedCore.RSS.Parsers;

namespace svs.Mobile.BeyondMedia.FeedCore.RSS
{
	public delegate void FeedUpdateEvent(Feed aFeed);
	public delegate void FeedUpdateErrorEvent(Feed aFeed, Exception ex);

	public class RSSFeedCatcher
	{
		#region Data Memebers

		private Feed m_Feed;
		private DownloadAgent m_FeedDownloadAgent;
		private DownloadAgent m_FeedImageDownloadAgent;
		private bool m_ForceUpdate = false;
		private DownloadStatistics m_Statistics;

		public event FeedUpdateEvent FeedDownloaded;
		public event FeedUpdateErrorEvent FeedUpdateError;
		public event FeedUpdateEvent FeedUpdateProgress;
		public event FeedUpdateEvent FeedUpdateStarted;
		public event FeedUpdateEvent FeedUpdateTerminated;

		#endregion

		#region Constructors

		public void InitializeCatcher(Feed feed)
		{
			m_Feed = feed;
		}

		#endregion

		#region Public Methods

		/// <summary>
		/// Downloads the latest content for this feed
		/// </summary>
		public void UpdateFeed(bool forceUpdate)
		{
			//Attempt to Download the feed under a temp name so we don't damage the cached version
			// if the download fails

			m_FeedDownloadAgent = null;  //clear the download agent from previous downloads
			m_Statistics = null;

			try
			{
				if(m_Feed.IsAggregate)
				{
					OnDownloadCompleted(AggregateFeedmanager.GenerateContent(m_Feed), true);
					return;
				}

				if(!m_Feed.HasUri)
				{
					throw new ApplicationException("Trying to update feed with invalid URL!");
				}

				m_FeedDownloadAgent = new DownloadAgent(m_Feed.FeedUri, RssFeedCache.GetFeedTempPath(m_Feed.FeedUri), m_Feed.Credentials);

				m_FeedDownloadAgent.DownloadCompleted += OnDownloadCompleted;
				m_FeedDownloadAgent.DownloadError += OnDownloadError;
				m_FeedDownloadAgent.DownloadProgress += OnDownloadProgress;
				m_FeedDownloadAgent.DownloadStarted += OnDownloadStarted;
				m_FeedDownloadAgent.DownloadTerminated += OnDownloadTerminated;

				m_FeedDownloadAgent.AllowResume = false;

				m_ForceUpdate = forceUpdate;

				if (!m_ForceUpdate)
				{
					long size = RssFeedCache.GetCachedFileSize(m_Feed.FeedUri);

					//If the old feed exists, check if a new version is available
					if (size != -1)
					{
						m_FeedDownloadAgent.IgnoreIfSize = size;
						m_FeedDownloadAgent.IgnoreIfNotModifiedSinceDate = m_Feed.LastServerModifiedDate;
					}
				}

				m_Feed.UpdateSize = m_Feed.UpdatedPortion = 0;
				m_FeedDownloadAgent.StartDownload();
			}
			catch(Exception ex)
			{
				CoreHelper.WriteLogEntry(string.Format("Unable to start the download of Url:{0}! Reason: {1}", m_Feed.FeedUrl, ex.Message));
				OnDownloadError(null, ex);
			}
		}

		/// <summary>
		/// Stops the download
		/// </summary>
		public void Stop()
		{
			if (m_FeedDownloadAgent != null)
				m_FeedDownloadAgent.Stop();

			if (m_FeedImageDownloadAgent != null)
				m_FeedImageDownloadAgent.Stop();
		}

		#endregion

		#region Public Properties

		/// <summary>
		/// True if the Catcher is currently updating a feed
		/// </summary>
		public bool IsUpdating
		{
			get { return m_FeedDownloadAgent != null && m_FeedDownloadAgent.IsDownloading; }
		}

		/// <summary>
		/// Timing statistics about the last download session
		/// </summary>
		internal DownloadStatistics Statistics
		{
			get { return m_Statistics; }
		}

		#endregion

		#region Private Methods

		private void OnDownloadTerminated(string destination)
		{
			CaptureStatistics();

			RssFeedCache.DeleteTempFeedCacheFor(m_Feed);

			if (FeedUpdateTerminated != null)
			{
				FeedUpdateTerminated(m_Feed);
			}
		}

		private void OnDownloadStarted(string destination)
		{
			m_Feed.UpdateFailed = false;

			if (FeedUpdateStarted != null)
			{
				FeedUpdateStarted(m_Feed);
			}
		}

		private void OnDownloadProgress(string destination, long current, long total)
		{
			if (FeedUpdateProgress != null)
			{
				m_Feed.UpdateSize = total;
				m_Feed.UpdatedPortion = current;

				FeedUpdateProgress(m_Feed);
			}
		}

		private void CaptureStatistics()
		{
			if (m_FeedDownloadAgent != null)
				m_Statistics = m_FeedDownloadAgent.Statistics;
		}

		private void OnDownloadError(string destination, Exception ex)
		{
			CaptureStatistics();

			m_Feed.UpdateFailed = true;

			RssFeedCache.DeleteTempFeedCacheFor(m_Feed);

			if (FeedUpdateError != null)
				FeedUpdateError(m_Feed, ex);
		}

		private void OnDownloadCompleted(string destination, bool hasNewContent)
		{
			CaptureStatistics();

			//Check if the new and the old are actually the same size
			if (hasNewContent || m_ForceUpdate)
			{
				PostProcessFeed();
			}
			else
			{
				RssFeedCache.TouchFeedCacheFor(m_Feed);
				RssFeedCache.DeleteTempFeedCacheFor(m_Feed);
			}

			m_Feed.UpdateFailed = false;

			//Now make sure we have the feed image
			if (FeedImageCache.HasImageToDownload(m_Feed))
			{
				DownloadImage();
			}
			else
			{
				OnFeedUpdateCompleted();
			}
		}

		public void DownloadImage()
		{
			try
			{
				m_FeedImageDownloadAgent =
					new DownloadAgent(CoreHelper.SafeCreateUri(m_Feed.FeedImageUrl), FeedImageCache.GetTempFeedImagePath(m_Feed), m_Feed.Credentials);

				m_FeedImageDownloadAgent.DownloadCompleted += OnFeedImageDownloadCompleted;
				m_FeedImageDownloadAgent.DownloadError += OnFeedImageDownloadFailed;

				m_FeedImageDownloadAgent.AllowResume = false;
				CoreHelper.WriteTraceEntry("Loading image for: " + m_Feed.FeedImageUrl);

				m_FeedImageDownloadAgent.StartDownload();
			}
			catch(Exception ex)
			{
				OnFeedImageDownloadFailed(string.Empty, ex);
			}
		}

		private void OnFeedImageDownloadFailed(string destination, Exception ex)
		{
			CoreHelper.WriteLogEntry(string.Format("Unable to download image for: {0}! Reason:{1}", m_Feed.FeedImageUrl, ex.Message));
			OnFeedUpdateCompleted();
		}

		private void OnFeedImageDownloadCompleted(string destination, bool ContentUpdated)
		{
			if (File.Exists(FeedImageCache.GetTempFeedImagePath(m_Feed)))
			{
				FeedImageCache.FormatImage(m_Feed);
				CoreHelper.WriteTraceEntry("Done Loading image for: " + m_Feed.FeedImageUrl);
			}
			OnFeedUpdateCompleted();
		}

		private void OnFeedUpdateCompleted()
		{
			if (FeedDownloaded != null)
			{
				FeedDownloaded(m_Feed);
			}
		}

		private void PostProcessFeed()
		{
			RssFeedCache.CopyTempToPrimaryForFeed(m_Feed);

			if (m_Feed.IsAggregate)  //no need to post process aggregate feeds
			{
				m_Feed.HasNewContent = true;
				return;
			}

			RssFeed rss = null;
			
			try 
			{
				rss = RssFeedCache.PeekFromCache(m_Feed, RssFeedParser.FeedParseLevel.ItemEssentials);
			}
			catch(CoreHelper.EncounteredOpmlFeedException)
			{
				rss = null;
			}

			if (rss != null)
			{
				if (!string.IsNullOrEmpty(rss.FeedImageLink))
					m_Feed.FeedImageUrl = rss.FeedImageLink;

				//Make sure our feed read status reflects what is in the feed
				rss.SyncOwnerFeedReadStatus();

				//If the feed has a published date, use it to determine if the feed is new
				DateTime? pubDate = (rss.Items.Count > 0 ? rss.Items[0].PubDate : null) ?? rss.PubDate;
				int? lastItemid = rss.Items.Count > 0 ? rss.Items[0].ItemID : null;

				if (pubDate.HasValue) // try the Pub Date first
				{
					if (m_Feed.LastModifiedDate != pubDate)
					{
						m_Feed.HasNewContent = true;
						m_Feed.LastModifiedDate = pubDate;
					}
				}
				else if(lastItemid.HasValue) // then try the id of the last (most recent item)
				{
					if (m_Feed.LastItemID != lastItemid)
					{
						m_Feed.HasNewContent = true;
						m_Feed.LastItemID = lastItemid;
					}
				}
				else 
				{
					m_Feed.HasNewContent = true;
					m_Feed.LastModifiedDate = GetLastModifiedDate();
				}

				m_Feed.LastServerModifiedDate = GetLastModifiedDate();
			}
		}

		private  DateTime? GetLastModifiedDate()
		{
			if(m_FeedDownloadAgent == null)
				return DateTime.Now;

			return m_FeedDownloadAgent.LastModifiedDate ?? DateTime.Now;
		}

		#endregion
	}
}